package com.hanter.demo.raddemo

import android.text.TextUtils
import android.util.Log
import android.widget.EditText
import androidx.collection.ArrayMap
import java.math.BigDecimal
import java.util.*


open class ValidatorWrapper private constructor(private val parent: ValidatorWrapper?, private val condition: Boolean = true){

    companion object {
        val validatorMap = ArrayMap<Class<*>, Class<out Validator>>()

        init {
            validatorMap[Number::class.java] = NumberValidator::class.java
            validatorMap[String::class.java] = StringValidator::class.java
            validatorMap[Array::class.java] = ArrayValidator::class.java
            validatorMap[Collection::class.java] = CollectionValidator::class.java
            validatorMap[Map::class.java] = MapValidator::class.java
        }

        fun of(str: String?): ValidatorWrapper {
            return ValidatorWrapper().of(str)
        }

        fun of(list: List<*>?): ValidatorWrapper {
            return ValidatorWrapper().of(list)
        }

        fun of(arr: Array<*>?): ValidatorWrapper {
            return ValidatorWrapper().of(arr)
        }

        fun of(map: Map<*, *>?): ValidatorWrapper {
            return ValidatorWrapper().of(map)
        }

        fun of(num: Int?): ValidatorWrapper {
            return ValidatorWrapper().of(num)
        }

        fun of(num: Byte?): ValidatorWrapper {
            return ValidatorWrapper().of(num)
        }

        fun of(num: Short?): ValidatorWrapper {
            return ValidatorWrapper().of(num)
        }

        fun of(num: Long?): ValidatorWrapper {
            return ValidatorWrapper().of(num)
        }
    }

    constructor() : this(null, true)

    protected var isElse: Boolean = false
    private var message: String? = null
    val list = ArrayList<ValidatorTriple<*>>()

    fun of(str: String?): ValidatorWrapper {
        this.list.add(ValidatorTriple(String::class.java, str, ValidatorChain()))
        return this
    }

    fun of(edit: EditText): ValidatorWrapper {
        this.list.add(ValidatorTriple(String::class.java, edit.text.toString(), ValidatorChain()))
        return this
    }

    fun of(list: List<*>?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Collection::class.java, list, ValidatorChain()))
        return this
    }

    fun of(arr: Array<*>?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Array::class.java, arr, ValidatorChain()))
        return this
    }

    fun of(map: Map<*, *>?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Map::class.java, map, ValidatorChain()))
        return this
    }

    fun of(num: Int?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Number::class.java, num, ValidatorChain()))
        return this
    }

    fun of(num: Byte?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Number::class.java, num, ValidatorChain()))
        return this
    }

    fun of(num: Short?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Number::class.java, num, ValidatorChain()))
        return this
    }

    fun of(num: Long?): ValidatorWrapper {
        this.list.add(ValidatorTriple(Number::class.java, num, ValidatorChain()))
        return this
    }

    fun of(num: Double?): ValidatorWrapper {
        val decimal = if (num != null) {
            BigDecimal(num)
        } else {
            null
        }
        this.list.add(ValidatorTriple(BigDecimal::class.java, decimal, ValidatorChain()))
        return this
    }

    fun ifExists(res: Boolean): ValidatorWrapper {
        return ValidatorWrapper(this, res)
    }

    fun ifExists(res: () -> Boolean): ValidatorWrapper {
        return ValidatorWrapper(this, res())
    }

    fun orElse(): ValidatorWrapper {
        this.isElse = true
        return this
    }

    fun end(): ValidatorWrapper {
        return this.parent?.let { wrapper ->
            wrapper.list.addAll(this.list)
            wrapper
        } ?: this
    }

    fun notNull(msg: String): ValidatorWrapper {
        this.lastChain()?.add(NotNull(msg))
        return this
    }

    open fun notEmpty(msg: String): ValidatorWrapper {
        this.lastChain()?.add(NotEmpty(msg))
        return this
    }

    fun min(min: Int, msg: String): ValidatorWrapper {
        this.lastChain()?.add(Min(min, msg))
        return this
    }

    fun max(max: Int, msg: String): ValidatorWrapper {
        this.lastChain()?.add(Max(max, msg))
        return this
    }

    fun size(min: Int, max: Int, msg: String): ValidatorWrapper {
        this.lastChain()?.add(Size(min, max, msg))
        return this
    }

    private fun lastChain(): ValidatorChain? {
        if (condition xor isElse) {
            if (this.list.isEmpty()) {
                return this.parent?.list?.last()?.chain
            } else {
                return this.list.last().chain
            }
        } else {
            return null
        }
    }

    fun validate(): Boolean {
        this.list.forEach { validatorTriple ->
            if (!validatorTriple.validate()) {
                this.message = validatorTriple.chain.getMessage()
                Log.d("Test", "message: ${this.message}")
                return false
            }
        }
        return true
    }

    fun getMessage(): String {
        return this.message ?: ""
    }

}

class Result(val result: Boolean, val message: String)

class ValidatorTriple<T>(val clazz: Class<T>,  val src: T?, val chain: ValidatorChain) {

    fun validate(): Boolean {
        val validatorClazz = ValidatorWrapper.validatorMap[clazz] ?: return true
        return try {
            val constructor = validatorClazz.getConstructor()
            val validator = constructor.newInstance()
            chain.validate(validator, src)
        } catch (e: Exception) {
            false
        }
    }

}

class ValidatorChain {

    private var index: Int = 0
    private val list = ArrayList<Constraint>()

    fun add(v: Constraint) {
        this.list.add(v)
    }

    fun validate(validator: Validator, src: Any?): Boolean {
        this.index = 0
        this.list.forEachIndexed { index, constraint ->
            if (!validator.validate(src, constraint)) {
                this.index = index
                return false
            }
        }
        return true
    }

    fun getMessage(): String {
        return this.list.getOrNull(this.index)?.getMessage() ?: ""
    }

}

interface Validator {
    fun validate(obj: Any?, constraint: Constraint): Boolean
}

interface ArrayValidator : Validator {
    override fun validate(obj: Any?, constraint: Constraint): Boolean {
        val array = obj as? Array<*>?

        return when (constraint) {
            is NotNull -> {
                obj != null
            }

            is NotEmpty -> {
                (array?.size ?: 0) > 0
            }

            is Min -> {
                (array?.size ?: 0) >= constraint.min
            }

            is Max -> {
                (array?.size ?: 0) <= constraint.max
            }

            is Size -> {
                (array?.size ?: 0) in constraint.min..constraint.max
            }

            else -> true
        }
    }
}

class CollectionValidator : Validator {
    override fun validate(obj: Any?, constraint: Constraint): Boolean {
        val collection = obj as? Collection<*>?

        return when (constraint) {
            is NotNull -> {
                obj != null
            }

            is NotEmpty -> {
                (collection?.size ?: 0) > 0
            }

            is Min -> {
                (collection?.size ?: 0) >= constraint.min
            }

            is Max -> {
                (collection?.size ?: 0) <= constraint.max
            }

            is Size -> {
                (collection?.size ?: 0) in constraint.min..constraint.max
            }

            else -> true
        }
    }
}

class MapValidator : Validator {
    override fun validate(obj: Any?, constraint: Constraint): Boolean {
        val map = obj as? Map<*, *>?

        return when (constraint) {
            is NotNull -> {
                obj != null
            }

            is NotEmpty -> {
                map?.isNotEmpty() == true
            }

            is Min -> {
                (map?.size ?: 0) >= constraint.min
            }

            is Max -> {
                (map?.size ?: 0) <= constraint.max
            }

            is Size -> {
                (map?.size ?: 0) in constraint.min..constraint.max
            }

            else -> true
        }
    }
}

class NumberValidator : Validator {
    override fun validate(obj: Any?, constraint: Constraint): Boolean {
        val number = (obj as? Number?)?.toLong()

        return when (constraint) {
            is NotNull -> {
                obj != null
            }

            is Min -> {
                (number ?: 0) >= constraint.min
            }

            is Max -> {
                (number ?: 0) <= constraint.max
            }

            is Size -> {
                (number ?: 0) in constraint.min..constraint.max
            }

            else -> true
        }
    }

}

class StringValidator : Validator {

    override fun validate(obj: Any?, constraint: Constraint): Boolean {
        val str = obj as? String?

        return when (constraint) {
            is NotNull -> {
                obj != null
            }

            is NotEmpty -> {
                !TextUtils.isEmpty(str)
            }

            is Min -> {
                (str?.length ?: 0) >= constraint.min
            }

            is Max -> {
                (str?.length ?: 0) <= constraint.max
            }

            is Size -> {
                (str?.length ?: 0) in constraint.min..constraint.max
            }

            else -> true
        }
    }

}

open class Constraint(private val msg: String) {
    fun getMessage(): String = msg
}

class NotNull(msg: String) : Constraint(msg)

class NotEmpty(msg: String) : Constraint(msg)

class Min(val min: Int, msg: String) : Constraint(msg)

class Max(val max: Int, msg: String) : Constraint(msg)

class Size(val min: Int, val max: Int, msg: String) : Constraint(msg)